package biz.datalk.industrialland.common.encrypt.sm;

import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.util.encoders.Hex;

import java.nio.charset.StandardCharsets;

/**
 * @author tarofang@163.com
 * @date 2022-06-28
 */
public class GMUtils extends GMBaseUtil {
    public static final byte UNCOMPRESSED_FLAG = 0x04;

    public static String sm2Encrypt(String pubKeyHex, String data) {
        return sm2Encrypt(SM2Engine.Mode.C1C3C2, pubKeyHex, data);
    }

    /**
     * SM2 加密
     * 按国密排序标准加密 C1C3C2
     *
     * @param mode      {@link SM2Engine.Mode}
     * @param pubKeyHex 加密的公钥(16进制字符串)
     * @param data      加密的原始字符串
     *
     * @return 密文
     *
     * @author tarofang@163.com
     * @date 2022年06月28日
     */
    public static String sm2Encrypt(SM2Engine.Mode mode, String pubKeyHex, String data) {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.createECPublicKeyParameters(pubKeyHex, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS);
        byte[] in = data.getBytes();
        try {
            byte[] encryptBytes = SM2Util.encrypt(mode, pubKeyParameters, in);
            // return ByteUtils.toHexString(encryptBytes).toLowerCase();
            return Hex.toHexString(encryptBytes).toLowerCase();
        } catch (Exception ex) {
            throw new SM2EncryptException(ex);
        }
    }

    public static String sm2Decrypt(String priKeyHex, String cipherDataHex) {
        return sm2Decrypt(SM2Engine.Mode.C1C3C2, priKeyHex, cipherDataHex);
    }

    /**
     * SM2 解密
     * 按国密排序标准加密 C1C3C2
     *
     * @param mode          {@link SM2Engine.Mode}
     * @param priKeyHex     加密的私钥(16进制字符串)
     * @param cipherDataHex sm2密文(16进制字符串)
     *
     * @return 明文
     *
     * @author tarofang@163.com
     * @date 2022年06月28日
     */
    public static String sm2Decrypt(SM2Engine.Mode mode, String priKeyHex, String cipherDataHex) {
        // 使用BC库加解密时密文以04开头，传入的密文前面没有04则补上
        if (!cipherDataHex.startsWith("04")) {
            cipherDataHex = "04" + cipherDataHex;
        }
        // byte[] sm2Cipher = ByteUtils.fromHexString(cipherDataHex);
        byte[] sm2Cipher = Hex.decode(cipherDataHex);
        ECPrivateKeyParameters privateKeyParameters = BCECUtil.createECPrivateKeyParameters(priKeyHex, SM2Util.DOMAIN_PARAMS);
        try {
            byte[] decryptBytes = SM2Util.decrypt(mode, privateKeyParameters, sm2Cipher);
            return new String(decryptBytes, StandardCharsets.UTF_8);
        } catch (Exception ex) {
            throw new SM2EncryptException(ex);
        }
    }

    /**
     * 签名
     *
     * @param priKeyHex 私钥
     * @param data      原文
     *
     * @return sm2签名结果, 16进制字符串(R | | S拼接的字节数组转换)
     */
    public static String sm2Sign(String priKeyHex, String data) {
        try {
            ECPrivateKeyParameters privateKeyParameters = BCECUtil.createECPrivateKeyParameters(priKeyHex, SM2Util.DOMAIN_PARAMS);
            byte[] in = data.getBytes(StandardCharsets.UTF_8);
            byte[] derSign = SM2Util.sign(privateKeyParameters, in);
            // 将 DER 编码转为 R||S (64字节数组，前32字节为R，后32字节为S)
            byte[] sm2Sign = SM2Util.decodeDERSM2Sign(derSign);
            // return ByteUtils.toHexString(sm2Sign).toLowerCase();
            return Hex.toHexString(sm2Sign).toLowerCase();
        } catch (Exception ex) {
            throw new SM2EncryptException(ex);
        }
    }

    public static boolean sm2SignVerify(String pubKeyHex, String srcData, String sm2SignHex) {
        try {
            // byte[] sm2SignBytes = ByteUtils.fromHexString(sm2SignHex);
            byte[] sm2SignBytes = Hex.decode(sm2SignHex);
            byte[] derSign = SM2Util.encodeSM2SignToDER(sm2SignBytes);
            ECPublicKeyParameters pubKeyParameters = BCECUtil.createECPublicKeyParameters(pubKeyHex, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS);
            return SM2Util.verify(pubKeyParameters, null, srcData.getBytes(StandardCharsets.UTF_8), derSign);
        } catch (Exception ex) {
            throw new SM2EncryptException(ex);
        }
    }

    public static String sm3(String str) {
        if (isBlank(str)) {
            throw new SM3EncryptException("Blank str.");
        }
        try {
            byte[] preHash = str.getBytes(StandardCharsets.UTF_8);
            byte[] hash = SM3Util.hash(preHash);
            return Hex.toHexString(hash).toLowerCase();
        } catch (Exception ex) {
            throw new SM3EncryptException(ex);
        }
    }

    public static boolean verifySm3(String str, String sm3Hex) {
        if (isAnyBlank(str, sm3Hex)) {
            throw new SM3EncryptException("Blank str or sm3Hex.");
        }
        return sm3Hex.equals(sm3(str));
    }

    public static String sm3Hmac(String str, String key) {
        if (isAnyBlank(str, key)) {
            throw new SM3EncryptException("Blank str or key.");
        }
        try {
            byte[] preHash = str.getBytes(StandardCharsets.UTF_8);
            byte[] preKey = key.getBytes(StandardCharsets.UTF_8);
            byte[] hmac = SM3Util.hmac(preHash, preKey);
            return Hex.toHexString(hmac).toLowerCase();
        } catch (Exception ex) {
            throw new SM3EncryptException(ex);
        }
    }

    public static boolean verifySm3Hmac(String str, String key, String sm3HmacHex) {
        if (isAnyBlank(str, key, sm3HmacHex)) {
            throw new SM3EncryptException("Blank str or key or sm3HmacHex.");
        }
        return sm3HmacHex.equals(sm3Hmac(str, key));
    }

    public static String sm4Encrypt(String data, String keyHex) {
        return sm4Encrypt(data, keyHex, null);
    }

    public static String sm4Encrypt(String data, String keyHex, String ivHex) {
        try {
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            // byte[] keyBytes = ByteUtils.fromHexString(keyHex);
            byte[] keyBytes = Hex.decode(keyHex);
            byte[] resBytes;
            if (ivHex == null) {
                resBytes = SM4Util.encryptECBPadding(keyBytes, dataBytes);
            } else {
                // byte[] ivBytes = ByteUtils.fromHexString(ivHex);
                byte[] ivBytes = Hex.decode(ivHex);
                resBytes = SM4Util.encryptCBCPadding(keyBytes, ivBytes, dataBytes);
            }
            // return ByteUtils.toHexString(resBytes).toLowerCase();
            return Hex.toHexString(resBytes).toLowerCase();
        } catch (Exception ex) {
            throw new SM4EncryptException(ex);
        }
    }

    public static String sm4Decrypt(String dataHex, String keyHex) {
        return sm4Decrypt(dataHex, keyHex, null);
    }

    public static String sm4Decrypt(String dataHex, String keyHex, String ivHex) {
        try {
            // byte[] dataBytes = ByteUtils.fromHexString(dataHex);
            byte[] dataBytes = Hex.decode(dataHex);
            // byte[] keyBytes = ByteUtils.fromHexString(keyHex);
            byte[] keyBytes = Hex.decode(keyHex);
            byte[] resBytes;
            if (ivHex == null) {
                resBytes = SM4Util.decryptECBPadding(keyBytes, dataBytes);
            } else {
                // byte[] ivBytes = ByteUtils.fromHexString(ivHex);
                byte[] ivBytes = Hex.decode(ivHex);
                resBytes = SM4Util.decryptCBCPadding(keyBytes, ivBytes, dataBytes);
            }
            return new String(resBytes, StandardCharsets.UTF_8);
        } catch (Exception ex) {
            throw new SM4EncryptException(ex);
        }
    }


}
